package com.jsftoolkit.gen.info.impl;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.util.Map;

import javax.faces.component.EditableValueHolder;

import org.xml.sax.SAXException;

import com.jsftoolkit.base.renderer.DecodeEvent;
import com.jsftoolkit.base.renderer.HtmlRenderer;
import com.jsftoolkit.base.renderer.PassThrough;
import com.jsftoolkit.base.renderer.RenderEventsCollector;
import com.jsftoolkit.base.renderer.VarAttribEvent;
import com.jsftoolkit.base.renderer.VarTextEvent;
import com.jsftoolkit.gen.info.ComponentInfo;
import com.jsftoolkit.gen.info.ComponentInfoProvider;
import com.jsftoolkit.gen.info.PropertyInfo;
import com.jsftoolkit.utils.Utils;
import com.jsftoolkit.utils.xmlpull.PullEvent;
import com.jsftoolkit.utils.xmlpull.StartElement;

/**
 * Scans an {@link HtmlRenderer} template for component information.
 * 
 * @author noah
 * 
 */
public class TemplateComponentInfoProvider implements ComponentInfoProvider {

	private RenderEventsCollector events;

	/**
	 * 
	 * @param template
	 *            the template to examine
	 * @throws IOException
	 * @throws SAXException
	 */
	public TemplateComponentInfoProvider(String template) throws IOException,
			SAXException {
		this.events = new RenderEventsCollector(template);
	}

	/**
	 * Updates info's property information based on information found in the
	 * template.
	 */
	public void fillIn(ComponentInfo info) {
		Map<String, PropertyInfo> properties = info.getProperties();

		for (PullEvent event : events) {
			switch (event.getType()) {
			case VarAttribEvent.TYPE: {
				VarAttribEvent ev = (VarAttribEvent) event;
				for (String property : ev.getProperties()) {
					String name = Utils.toConstantName(property);
					if (!properties.containsKey(name)
							&& !isIgnoreProperty(property)) {
						properties.put(name, new PropertyInfo(null, property));
					}
				}
			}
				break;
			case VarTextEvent.TYPE: {
				VarTextEvent ev = (VarTextEvent) event;

				String name = Utils.toConstantName(ev.getProperty());
				if (!properties.containsKey(name)
						&& !isIgnoreProperty(ev.getProperty())) {
					properties.put(name, new PropertyInfo(null, ev
							.getProperty(), ev.getDefaultValue()));
				}
			}
				break;
			case PassThrough.TYPE: {
				PassThrough ev = (PassThrough) event;

				// remove pass through attributes that already have a
				// getter/setter pair in the super class, because it is not
				// necessary to have another get/set pair
				try {
					for (PropertyDescriptor pd : info.getTag()
							.getPropertyDescriptors()) {
						ev.getAllowed().remove(pd.getName());
					}
				} catch (IntrospectionException e) {
					// if the introspector fails, there is nothing we can do
					throw new RuntimeException(e);
				}

				info.getRenderer().getAttribs().addAll(ev.getAllowed());
			}
				break;
			case StartElement.TYPE: {
				StartElement ev = (StartElement) event;
				if (ev.getName().startsWith(HtmlRenderer.TAG_PREFIX)) {
					info.getRenderer().addAttribute(HtmlRenderer.TAG);
				}
			}
				break;
			case DecodeEvent.TYPE: {
				info.getRenderer().setDecodeParam(
						((DecodeEvent) event).getValue());
				info.addInterface(EditableValueHolder.class);
			}
			default:
				// don't care about other events
			}
		}
	}

	protected boolean isIgnoreProperty(String property) {
		return "id".equalsIgnoreCase(property)
				|| "binding".equalsIgnoreCase(property)
				|| "rendered".equalsIgnoreCase(property);
	}

}
